package saxion.speelveld.spaceengineer.model;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Observable;

import saxion.speelveld.spaceengineer.buildings.Mines;
import android.util.Log;

public class Game extends Observable implements Serializable
{

	/**
	 * 
	 */
	private static final long serialVersionUID = 5952789434784680043L;

	private enum Orientatie
	{
		NORTH, EAST, SOUTH, WEST
	};

	private int gameId;

	private Tile[][] gameField;

	private ArrayList<Building> buildings;

	private int viewCursorX, viewCursorY;

	private int gameTime;

	private Difficulties difficulty;

	private Resource[] resources;

	private Tile selectedTile;

	private Building queuedBuilding;

	public int queuedBuildingX = 0;

	public int queuedBuildingY = 0;

	private TradePlanet[] tradePlanets;

	private boolean buildInfrastructure;

	private int tilesOverX;

	private int tilesOverY;

	private boolean buildingsUpdated;

	private boolean resourcesUpdated;

	private static boolean tilesUpdated = false;

	private boolean newGame = true;

	private ArrayList<RemovedBuilding> removedBuildings;

	public Difficulties getDifficulty()
	{
		return difficulty;
	}

	public void setDifficulty(Difficulties difficulty)
	{
		this.difficulty = difficulty;
	}

	/**
	 * used to start a game
	 * 
	 * @param GameField
	 * @param GameTime
	 * @param difficultie
	 */
	public Game(Tile[][] gameField, ArrayList<Building> buildings, int gameTime, Difficulties difficultie,
		Resource[] resources, TradePlanet[] tradePlanets)
	{
		this.buildings = buildings;
		this.gameField = gameField;
		this.setDifficulty(difficultie);
		this.setGameTime(gameTime);
		this.resources = resources;
		this.tradePlanets = tradePlanets;
		this.removedBuildings = new ArrayList<RemovedBuilding>();
	}

	/**
	 * @param Width
	 *            how many Tiles are shown on the X-axes.
	 * @param Height
	 *            how many Tiles are shown on the Y-axes.
	 * @return the part of the field where the player is currently looking at.
	 */
	public Tile[][] GetFieldToShow(int Width, int Height, Tile[][] currentfield)
	{
		// TODO out of bounds checken.
		if (currentfield == null)
		{
			currentfield = new Tile[Width][Height];
		}

		// Tile[][] toreturn = new Tile[Width][Height];

		for (int x = 0; x < Width; x++)
		{
			for (int y = 0; y < Height; y++)
			{
				currentfield[x][y] = gameField[viewCursorX + x][viewCursorY + y];
			}
		}
		return currentfield;
	}

	/**
	 * This method sets both the x and the y location of the ViewCursor
	 * 
	 * @param x
	 *            The X location of the Tile of the left-upper corner of your viewField
	 * @param y
	 *            The Y location of the Tile on the left-upper corner of your viewField
	 */
	public void setViewCursor(int x, int y)
	{
		// TODO out of bounds check;
		if (x > 0 && y > 0 && x < 100 && y < 100)
		{
			viewCursorX = x;
			viewCursorY = y;
			setChanged();
			notifyObservers(true);
		}
	}

	public void setViewCursorNoUpdate(int x, int y)
	{
		if (x > 0 && y > 0 && x < 100 && y < 100)
		{
			viewCursorX = x;
			viewCursorY = y;
		}
	}

	public void setViewPointToMiddleOf(int x, int y, int buildingSizeX, int buildingSizeY)
	{
		Log.d("setViewPointToMiddleOf", "x: " + x + " - y: " + y);
		Log.d("setViewPointToMiddleOf", "tx: " + tilesOverX + " - ty: " + tilesOverY);
		int middleX = x - (int) (this.tilesOverX / 2);
		int middleY = y - (int) (this.tilesOverY / 2);

		middleX += (int) Math.floor(buildingSizeX / 2);
		middleY += (int) Math.floor(buildingSizeY / 2);

		// Log.d("setViewPointToMiddleOf", "x: " + middleX + " - y: " + middleY);
		setViewCursor(middleX, middleY);
	}

	public int getViewCursorX()
	{
		return viewCursorX;
	}

	public int getViewCursorY()
	{
		return viewCursorY;
	}

	/**
	 * This method sets the X location of the ViewCursor
	 * 
	 * @param x
	 *            The X location of the Tile of the left-upper corner of your viewField
	 */
	public void setViewCursorX(int x)
	{
		// TODO out of bounds check;
		viewCursorX = x;
		setChanged();
		notifyObservers(true);
	}

	/**
	 * This method sets the Y location of the ViewCursor
	 * 
	 * @param y
	 *            The Y location of the Tile on the left-upper corner of your viewField
	 */
	public void setViewCursorY(int y)
	{
		// TODO out of bounds check;
		viewCursorY = y;
		setChanged();
		notifyObservers(true);
	}

	public Tile getSelectedTile()
	{
		return selectedTile;
	}

	/**
	 * 
	 * @param type
	 * @return the amount of resources that the user has.
	 */
	public int getResourceAmount(ResourceType type)
	{
		return resources[type.ordinal()].getAmount();
	}

	public Resource[] getResourceArray()
	{
		return this.resources;
	}

	public Resource[] getViewableResourceArray()
	{
		ArrayList<Resource> resourcesList = new ArrayList<Resource>();
		Resource[] resourceArray = new Resource[this.resources.length - 2];
		for (int i = 0; i < this.resources.length; i++)
		{
			Resource r = this.resources[i];
			if (r.getResourceType() != ResourceType.STORAGESPACE && r.getResourceType() != ResourceType.POPULATIONCAP)
			{
				resourcesList.add(r);
			}
		}
		resourcesList.toArray(resourceArray);
		return resourceArray;
	}

	/**
	 * Sets the value of a resource. Happiness may not have a value higher as 100 or lower as 0(will be checked)
	 * 
	 * @param Resource
	 *            Type
	 * @param value
	 */
	public void setResourceAmount(ResourceType type, int value)
	{
		if (type == ResourceType.HAPPINESS)
		{
			if (value >= 0 && value <= 100)
			{
				resources[type.ordinal()].setAmount(value);
			}
		}
		else
		{
			resources[type.ordinal()].setAmount(value);
		}

		Log.d("GameSetResourceAmount", "setChanged");
		setResourcesUpdated(true);
		setChanged();
		notifyObservers();
	}

	public void addResourceAmount(ResourceType type, int value)
	{
		if (type == ResourceType.HAPPINESS)
		{
			setResourceAmount(type, getResourceAmount(type) + value);
		}
		else if (type == ResourceType.STORAGESPACE)
		{
			resources[type.ordinal()].setAmount(getResourceAmount(type) + value);
		}
		else
		{
			if (getResourceAmount(type) + value < getResourceAmount(ResourceType.STORAGESPACE))
			{
				resources[type.ordinal()].setAmount(getResourceAmount(type) + value);

			}
			else
			{
				resources[type.ordinal()].setAmount(getResourceAmount(ResourceType.STORAGESPACE));
			}
			setResourcesUpdated(true);
		}
	}

	public void substractResourceAmount(ResourceType type, int value)
	{
		if (type == ResourceType.HAPPINESS)
		{
			setResourceAmount(type, getResourceAmount(type) - value);
		}
		else
		{
			resources[type.ordinal()].setAmount(getResourceAmount(type) - value);
			setResourcesUpdated(true);
		}
	}

	/**
	 * Set the building that is selected for the menu
	 * 
	 * @param selectedBuilding
	 */
	public void setSelectedTile(Tile selectedTile)
	{
		this.selectedTile = selectedTile;
		setChanged();
		notifyObservers(true);
	}

	public Tile[][] getField()
	{
		return gameField;
	}

	/**
	 * 
	 * @param usexandy
	 *            if this is true de method will use the profided x and y if false it wil use the queuedbuildingx and
	 *            queuedbuildingy
	 * @param ignoreTileType
	 *            if true it will place the building no matter what tile is already present use with caution!
	 * @param upperLeftX
	 *            from building on the current field that is shown on the gamescreen
	 * @param upperleftY
	 *            from building on the current field that is shown on the gamescreen
	 */
	// MS 14-03: return type veranderd naar boolean (i.p.v. void) voor check of het goed gegaan is.
	public boolean AddBuilding(int upperLeftx, int upperLefty, boolean usexandy, boolean ignoreTileType)
	{
		int buildingx = 0;
		int buildingy = 0;
		if (usexandy)
		{
			buildingx = upperLeftx;
			buildingy = upperLefty;
		}
		else
		{
			buildingx = queuedBuildingX + viewCursorX;
			buildingy = queuedBuildingY + viewCursorY;
		}
		ResourceType type = null;
		boolean ok = true;
		if (queuedBuilding != null)
		{
			if (!ignoreTileType)
			{
				for (int x = 0; x < queuedBuilding.getWidth(); x++)
				{
					for (int y = 0; y < queuedBuilding.getHeight(); y++)
					{
						if (gameField[buildingx + x][buildingy + y].getClass() != Tile.class)
						{
							if (queuedBuilding instanceof Mines
								&& gameField[buildingx + x][viewCursorY + queuedBuildingY + y]
									.getClass() == ResourceTile.class)
							{
								if (type == null)
								{
									type = ((ResourceTile) gameField[buildingx + x][buildingy + y]).getResourceType();
								}
								else
								{
									ok = false;
								}
							}
							else
							{
								ok = false;
							}
						}
					}
				}
			}
			if (queuedBuilding instanceof Mines)
			{
				if (type == null)
				{
					ok = false;
				}
				else
				{
					((Mines) queuedBuilding).setResourceProduceGain(new Resource(type, 1, true));
				}
			}
			if (ok)
			{
				Log.d("c'est moi bitches", "building is toegevoegd " + queuedBuilding.getBuildingName());
				boolean shouldBeConnected = false;
				for (int x = 0; x < queuedBuilding.getWidth(); x++)
				{
					for (int y = 0; y < queuedBuilding.getHeight(); y++)
					{
						int xtot = buildingx + x;
						int ytot = buildingy + y;
						gameField[xtot][ytot] = new BuildingTile(
							gameField[xtot][ytot].getBackgroundID(),
							queuedBuilding, x, y);
						if (isUnConInfOrBuilding(xtot - 1, ytot, true) || SupposedCon(xtot - 1, ytot, true))
						{
							ConnectTo(xtot - 1, ytot, Orientatie.EAST);
							if (SupposedCon(xtot - 1, ytot, true))
							{
								shouldBeConnected = true;
							}
						}
						if (isUnConInfOrBuilding(xtot + 1, ytot, true) || SupposedCon(xtot + 1, ytot, true))
						{
							ConnectTo(xtot + 1, ytot, Orientatie.WEST);
							if (SupposedCon(xtot + 1, ytot, true))
							{
								shouldBeConnected = true;
							}
						}
						if (isUnConInfOrBuilding(xtot, ytot - 1, true) || SupposedCon(xtot, ytot - 1, true))
						{
							ConnectTo(xtot, ytot - 1, Orientatie.SOUTH);
							if (SupposedCon(xtot, ytot - 1, true))
							{
								shouldBeConnected = true;
							}
						}
						if (isUnConInfOrBuilding(xtot, ytot + 1, true) || SupposedCon(xtot, ytot + 1, true))
						{
							ConnectTo(xtot, ytot + 1, Orientatie.NORTH);
							if (SupposedCon(xtot, ytot + 1, true))
							{
								shouldBeConnected = true;
							}
						}

					}
				}

				if (shouldBeConnected)
				{
					setInfOrBuildingConnected(viewCursorX + queuedBuildingX, viewCursorY + queuedBuildingY,
						queuedBuilding);
				}

				queuedBuilding.setBuildingPointInFieldX(buildingx);
				queuedBuilding.setBuildingPointInFieldY(buildingy);

				queuedBuildingX = 3;
				queuedBuildingY = 3;

				for (Resource r : queuedBuilding.resourceCosts)
				{
					substractResourceAmount(r.getResourceType(), r.getAmount());
				}

				if (queuedBuilding instanceof ProducingBuilding)
				{
					ArrayList<Resource> resourcesToAdd = ((ProducingBuilding) queuedBuilding)
						.getResourceProducedGainsOnce();
					for (Resource resourceToAdd : resourcesToAdd)
					{
						addResourceAmount(resourceToAdd.getResourceType(), resourceToAdd.getAmount());

					}
				}
				// moved from rule 205
				buildings.add(queuedBuilding);
				queuedBuilding = null;

				this.setChanged();
				this.notifyObservers(true);
			}
		}

		setBuildingsUpdated(true);
		return ok;
	}

	public boolean removeBuilding(Building building, boolean smallRefund, boolean fullRefund)
	{
		for (int x = building.getBuildingPointInFieldX(); x < building.getBuildingPointInFieldX() + building.getWidth(); x++)
		{
			for (int y = building.getBuildingPointInFieldY(); y < building.getBuildingPointInFieldY()
				+ building.getWidth(); y++)
			{
				gameField[x][y] = new Tile(gameField[x][y].getBackgroundID());
			}
		}
		removedBuildings.add(new RemovedBuilding(building, buildings.indexOf(building)));
		buildings.remove(building);

		for (Resource r : building.resourceCosts)
		{
			if (r.getResourceType() == ResourceType.ELECTRICITY)
			{
				addResourceAmount(ResourceType.ELECTRICITY, r.getAmount());
			}
			if (r.getResourceType() == ResourceType.HAPPINESS)
			{
				addResourceAmount(ResourceType.HAPPINESS, r.getAmount());
			}
		}

		if (queuedBuilding instanceof ProducingBuilding)
		{
			ArrayList<Resource> resourcesToAdd = ((ProducingBuilding) queuedBuilding)
				.getResourceProducedGainsOnce();
			for (Resource resourceToAdd : resourcesToAdd)
			{
				substractResourceAmount(resourceToAdd.getResourceType(), resourceToAdd.getAmount());

			}
		}

		buildingsUpdated = true;
		forceUpdate(true);
		// refund? no idea
		return true;
	}

	/**
	 * sets an infrastructure to connected and updates this to all other connected to it
	 * 
	 * @param posInFieldX
	 *            the X posistion in the field
	 * @param posInFieldY
	 *            the Y posistion in the field
	 * @param b
	 *            its just needed, if you dont know what your doing use null
	 */
	public void setInfOrBuildingConnected(int posInFieldX, int posInFieldY, Building b)
	{
		if (gameField[posInFieldX][posInFieldY].getClass() == InfrastructureTile.class)
		{
			((InfrastructureTile) gameField[posInFieldX][posInFieldY]).setConnected(true);
			if (isUnConInfOrBuilding(posInFieldX + 1, posInFieldY, false))
			{
				setInfOrBuildingConnected(posInFieldX + 1, posInFieldY, null);
			}
			if (isUnConInfOrBuilding(posInFieldX - 1, posInFieldY, false))
			{
				setInfOrBuildingConnected(posInFieldX - 1, posInFieldY, null);
			}
			if (isUnConInfOrBuilding(posInFieldX, posInFieldY + 1, false))
			{
				setInfOrBuildingConnected(posInFieldX, posInFieldY + 1, null);
			}
			if (isUnConInfOrBuilding(posInFieldX, posInFieldY - 1, false))
			{
				setInfOrBuildingConnected(posInFieldX, posInFieldY - 1, null);
			}
		}
		if (gameField[posInFieldX][posInFieldY].getClass() == BuildingTile.class)
		{
			Building building = ((BuildingTile) gameField[posInFieldX][posInFieldY]).getBuilding();
			if (building == b || b == null)
			{
				building.setConnectedToSpaceCenter(true);
				for (int x = building.getBuildingPointInFieldX(); x < building.getBuildingPointInFieldX()
					+ building.getWidth(); x++)
				{
					for (int y = building.getBuildingPointInFieldY(); y < building.getBuildingPointInFieldY()
						+ building.getHeight(); y++)
					{
						if (isUnConInfOrBuilding(x + 1, y, true))
						{
							setInfOrBuildingConnected(x + 1, y, b);
						}
						if (isUnConInfOrBuilding(x - 1, y, true))
						{
							setInfOrBuildingConnected(x - 1, y, b);
						}
						if (isUnConInfOrBuilding(x, y + 1, true))
						{
							setInfOrBuildingConnected(x, y + 1, b);
						}
						if (isUnConInfOrBuilding(x, y - 1, true))
						{
							setInfOrBuildingConnected(x, y - 1, b);
						}
					}
				}
			}
		}

	}

	public boolean isUnConInfOrBuilding(int x, int y, boolean ignorebuilding)
	{
		if (!(x >= 0 && x < gameField.length && y >= 0 && y < gameField[0].length))
		{
			return false;
		}
		if (gameField[x][y].getClass().equals(InfrastructureTile.class))
		{
			return !((InfrastructureTile) gameField[x][y]).isConnected();
		}
		if (gameField[x][y].getClass().equals(BuildingTile.class) && !ignorebuilding)
		{
			return !((BuildingTile) gameField[x][y]).getBuilding().isConnectedToSpaceCenter();
		}
		return false;
	}

	/**
	 * 
	 * @param x
	 * @param y
	 * @return if the given tile is connected returns true
	 */
	public boolean SupposedCon(int x, int y, boolean ignorebuilding)
	{
		if (!(x >= 0 && x < gameField.length && y >= 0 && y < gameField[0].length))
		{
			return false;
		}
		if (!gameField[x][y].getClass().equals(InfrastructureTile.class)
			&& !gameField[x][y].getClass().equals(BuildingTile.class))
			return false;

		if (gameField[x][y].getClass() == InfrastructureTile.class)
		{
			if (((InfrastructureTile) gameField[x][y]).isConnected())
			{
				return true;
			}
		}
		else if (gameField[x][y].getClass() == BuildingTile.class && !ignorebuilding)
		{
			if (((BuildingTile) gameField[x][y]).getBuilding().isConnectedToSpaceCenter())
			{
				Log.d("test124", "zou geconnect moeten worden");
				return true;
			}
		}
		else
		{
			return false;
		}
		return false;

	}

	public void ConnectTo(int x, int y, Orientatie o)
	{
		if (gameField[x][y].getClass() == InfrastructureTile.class)
		{
			switch (o)
			{
			case EAST:
				((InfrastructureTile) gameField[x][y]).setConnectedRight(true);
				break;
			case NORTH:
				((InfrastructureTile) gameField[x][y]).setConnectedTop(true);
				break;
			case SOUTH:
				((InfrastructureTile) gameField[x][y]).setConnectedBottom(true);
				break;
			case WEST:
				((InfrastructureTile) gameField[x][y]).setConnectedLeft(true);
				break;
			default:
				break;
			}

		}
	}

	// als gebouwen toch geen grondstoffen mogen geven booleans op true zetten
	public boolean addinfrastructure()
	{
		int x = viewCursorX + queuedBuildingX;
		int y = viewCursorY + queuedBuildingY;
		if (gameField[x][y].getClass() == Tile.class)
		{
			gameField[x][y] = new InfrastructureTile(gameField[x][y].getBackgroundID());
			if (SupposedCon(x - 1, y, false) || isUnConInfOrBuilding(x - 1, y, false))
			{
				ConnectTo(x - 1, y, Orientatie.EAST);
				ConnectTo(x, y, Orientatie.WEST);
				if (SupposedCon(x - 1, y, false))
				{
					setInfOrBuildingConnected(x, y, null);
				}
			}
			if (SupposedCon(x, y - 1, false) || isUnConInfOrBuilding(x, y - 1, false))
			{
				ConnectTo(x, y - 1, Orientatie.SOUTH);
				ConnectTo(x, y, Orientatie.NORTH);
				if (SupposedCon(x, y - 1, false))
				{
					setInfOrBuildingConnected(x, y, null);
				}

			}
			if (SupposedCon(x + 1, y, false) || isUnConInfOrBuilding(x + 1, y, false))
			{
				ConnectTo(x + 1, y, Orientatie.WEST);
				ConnectTo(x, y, Orientatie.EAST);
				if (SupposedCon(x + 1, y, false))
				{
					setInfOrBuildingConnected(x, y, null);
				}
			}
			if (SupposedCon(x, y + 1, false) || isUnConInfOrBuilding(x, y + 1, false))
			{
				ConnectTo(x, y + 1, Orientatie.NORTH);
				ConnectTo(x, y, Orientatie.SOUTH);
				if (SupposedCon(x, y + 1, false))
				{
					setInfOrBuildingConnected(x, y, null);
				}
			}

		}
		else
		{
			return false;
		}
		setChanged();
		notifyObservers(true);
		return true;
	}

	public ArrayList<Building> getBuildings()
	{
		return this.buildings;
	}

	public Building getQueuedBuilding()
	{
		return queuedBuilding;
	}

	public void setQueuedBuilding(Building queuedBuilding)
	{
		this.queuedBuilding = queuedBuilding;
		buildInfrastructure = false;
		setChanged();
		notifyObservers(true);

	}

	public int getTilesOverX()
	{
		return tilesOverX;
	}

	public void setTilesOverX(int tilesOverX)
	{
		this.tilesOverX = tilesOverX;
	}

	public int getTilesOverY()
	{
		return tilesOverY;
	}

	public void setTilesOverY(int tilesOverY)
	{
		this.tilesOverY = tilesOverY;
	}

	public TradePlanet[] getTradePlantes()
	{
		return tradePlanets;
	}

	public boolean isBuildInfrastructure()
	{
		return buildInfrastructure;
	}

	public void setBuildInfrastructure(boolean buildInfrastructure)
	{
		queuedBuilding = null;
		this.buildInfrastructure = buildInfrastructure;
		setChanged();
		notifyObservers(true);
	}

	public int getGameTime()
	{
		return gameTime;
	}

	public void setGameTime(int gameTime)
	{
		this.gameTime = gameTime;
	}

	public void tick()
	{
		this.gameTime += 1;

	}

	public int getGameId()
	{
		return gameId;
	}

	public void setGameId(int gameId)
	{
		this.gameId = gameId;
	}

	public void forceUpdate(boolean updatedBuildings)
	{
		setChanged();
		if (updatedBuildings == true)
		{
			notifyObservers(updatedBuildings);
		}
		else
		{
			notifyObservers();
		}
	}

	public boolean isBuildingsUpdated()
	{
		return buildingsUpdated;
	}

	public boolean isResourcesUpdated()
	{
		return resourcesUpdated;
	}

	public void setBuildingsUpdated(boolean buildingsUpdated)
	{
		this.buildingsUpdated = buildingsUpdated;
	}

	public void setResourcesUpdated(boolean resourcesUpdated)
	{
		this.resourcesUpdated = resourcesUpdated;
	}

	public static boolean isTilesUpdated()
	{
		return tilesUpdated;
	}

	public static void setTilesUpdated(boolean tilesUpdated)
	{
		Game.tilesUpdated = tilesUpdated;
	}

	public boolean isNewGame()
	{
		return newGame;
	}

	public void setNewGame(boolean newGame)
	{
		this.newGame = newGame;
	}

	public ArrayList<RemovedBuilding> getRemovedBuildings()
	{
		return removedBuildings;
	}

	public void upgradeBuilding(Building building)
	{
		if (buildings.contains(building))
		{
			if (building instanceof ProducingBuilding)
			{
				building.upgradeBuildingLevel();
				ProducingBuilding b = (ProducingBuilding) building;
				// ArrayList<Resource> gains = b.getResourcesProducedGains();
				// for (int i = 0; i < gains.size(); i++)
				// {
				// if (gains.get(i).isReOccuring() == true)
				// {
				// gains.get(i).setAmount(gains.get(i).getAmount() + 1);
				// }
				// }
				ArrayList<Resource[]> possibleProducements = b.getPossibleResourceProducements();
				if (possibleProducements.size() != 0)
				{
					for (int i = 0; i < possibleProducements.size(); i++)
					{
						float costPerPiece = possibleProducements.get(i)[1].getAmount()
							/ possibleProducements.get(i)[0].getAmount();

						possibleProducements.get(i)[0].setAmount(possibleProducements.get(i)[0].getAmount()
							+ 1);

						possibleProducements.get(i)[1].setAmount((int) (costPerPiece * possibleProducements.get(i)[0]
							.getAmount()));
					}
				}
			}
		}
	}
}
